package series

import (
	"fmt"
	"math"
	"strconv"
)

type float64Element struct {
	e float64
}

// force float64Element struct to implement Element interface
var _ Element = (*float64Element)(nil)

func (e *float64Element) Set(value interface{}) error {
	switch val := value.(type) {
	case string:
		f, err := strconv.ParseFloat(value.(string), 64)
		if err != nil {
			e.e = math.NaN()
			return fmt.Errorf("not a number")
		}
		e.e = f
	case int:
		e.e = float64(val)
	case int8:
		e.e = float64(val)
	case int16:
		e.e = float64(val)
	case int32:
		e.e = float64(val)
	case int64:
		e.e = float64(val)
	case uint:
		e.e = float64(val)
	case uint8:
		e.e = float64(val)
	case uint16:
		e.e = float64(val)
	case uint32:
		e.e = float64(val)
	case uint64:
		e.e = float64(val)
	case float32:
		e.e = float64(val)
	case float64:
		e.e = val
	case bool:
		b := val
		if b {
			e.e = 1
		} else {
			e.e = 0
		}
	case Element:
		e.e = val.Float64()
	default:
	}
	return nil
}

func (e float64Element) Copy() Element {
	return &float64Element{e.e}
}

func (e float64Element) IsNA() bool {
	return math.IsNaN(e.e)
}

func (e float64Element) Type() Type {
	return Float64
}

func (e float64Element) Val() ElementValue {
	if e.IsNA() {
		return nil
	}
	return float64(e.e)
}

func (e float64Element) String() string {
	if e.IsNA() {
		return "NaN"
	}
	return fmt.Sprintf("%f", e.e)
}

func (e float64Element) Int() (int, error) {
	if e.IsNA() {
		return 0, fmt.Errorf("can't convert NaN to int")
	}
	f := e.e
	if math.IsInf(f, 1) || math.IsInf(f, -1) {
		return 0, fmt.Errorf("can't convert Inf to int")
	}
	return int(f), nil
}

func (e float64Element) Int8() (int8, error) {
	if e.IsNA() {
		return 0, fmt.Errorf("can't convert NaN to int")
	}
	f := e.e
	if math.IsInf(f, 1) || math.IsInf(f, -1) {
		return 0, fmt.Errorf("can't convert Inf to int")
	}
	return int8(f), nil
}

func (e float64Element) Int16() (int16, error) {
	if e.IsNA() {
		return 0, fmt.Errorf("can't convert NaN to int")
	}
	f := e.e
	if math.IsInf(f, 1) || math.IsInf(f, -1) {
		return 0, fmt.Errorf("can't convert Inf to int")
	}
	return int16(f), nil
}

func (e float64Element) Int32() (int32, error) {
	if e.IsNA() {
		return 0, fmt.Errorf("can't convert NaN to int")
	}
	f := e.e
	if math.IsInf(f, 1) || math.IsInf(f, -1) {
		return 0, fmt.Errorf("can't convert Inf to int")
	}
	return int32(f), nil
}

func (e float64Element) Int64() (int64, error) {
	if e.IsNA() {
		return 0, fmt.Errorf("can't convert NaN to int")
	}
	f := e.e
	if math.IsInf(f, 1) || math.IsInf(f, -1) {
		return 0, fmt.Errorf("can't convert Inf to int")
	}
	return int64(f), nil
}

func (e float64Element) Float32() float32 {
	if e.IsNA() {
		return float32(math.NaN())
	}
	return float32(e.e)
}

func (e float64Element) Float64() float64 {
	if e.IsNA() {
		return math.NaN()
	}
	return float64(e.e)
}

func (e float64Element) Bool() (bool, error) {
	if e.IsNA() {
		return false, fmt.Errorf("can't convert NaN to bool")
	}
	switch e.e {
	case 1:
		return true, nil
	case 0:
		return false, nil
	}
	return false, fmt.Errorf("can't convert Float64 \"%v\" to bool", e.e)
}

func (e float64Element) Eq(elem Element) bool {
	f := elem.Float64()
	if e.IsNA() || math.IsNaN(f) {
		return false
	}
	return e.e == f
}

func (e float64Element) Neq(elem Element) bool {
	f := elem.Float64()
	if e.IsNA() || math.IsNaN(f) {
		return false
	}
	return e.e != f
}

func (e float64Element) Less(elem Element) bool {
	f := elem.Float64()
	if e.IsNA() || math.IsNaN(f) {
		return false
	}
	return e.e < f
}

func (e float64Element) LessEq(elem Element) bool {
	f := elem.Float64()
	if e.IsNA() || math.IsNaN(f) {
		return false
	}
	return e.e <= f
}

func (e float64Element) Greater(elem Element) bool {
	f := elem.Float64()
	if e.IsNA() || math.IsNaN(f) {
		return false
	}
	return e.e > f
}

func (e float64Element) GreaterEq(elem Element) bool {
	f := elem.Float64()
	if e.IsNA() || math.IsNaN(f) {
		return false
	}
	return e.e >= f
}
